/*------------------------------------------------------------------------------*\
**   Copyright 2008-2009 Jonhnny Weslley                                        **
**                                                                              **
**   Licensed under the Apache License, Version 2.0 (the "License");            **
**   you may not use this file except in compliance with the License.           **
**   You may obtain a copy of the License at                                    **
**                                                                              **
**       http://www.apache.org/licenses/LICENSE-2.0                             **
**                                                                              **
**   Unless required by applicable law or agreed to in writing, software        **
**   distributed under the License is distributed on an "AS IS" BASIS,          **
**   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   **
**   See the License for the specific language governing permissions and        **
**   limitations under the License.                                             **
\*------------------------------------------------------------------------------*/

package org.scalashell.scalash

import java.io.{File, FileReader, IOException, PrintWriter}

import scala.tools.nsc._
import scala.tools.nsc.reporters._

class ScalaShell(settings: Settings, var shell: InteractiveShell, out: PrintWriter) {

  def this(settings: Settings, shell: InteractiveShell) = this(settings, shell, new PrettyPrinter(Console.out))

  /** The context class loader at the time this object was created */
  protected val originalClassLoader = currentThread.getContextClassLoader

  var interpreter: Interpreter = null
  def isettings = interpreter.isettings

  var parentClassLoader0: ClassLoader = _

  private final val scalarc = new java.io.File(ShellSystem("user.home"),  ".scalarc")

  /** Create a new interpreter. Close the old one, if there is one. */
  def createInterpreter() {
    interpreter = new Interpreter(settings, out) {
      override protected def parentClassLoader = parentClassLoader0
      override protected def newCompiler(settings: Settings, reporter: Reporter) = {
        val global = new scala.tools.nsc.Global(settings, reporter)
        global
      }
    }
    interpreter.setContextClassLoader()
  }

  /** Close the interpreter, if there is one, and set interpreter to <code>null</code>. */
  def closeInterpreter() {
    if (interpreter ne null) {
      interpreter.close
      interpreter = null
      currentThread.setContextClassLoader(originalClassLoader)
    }
  }

  /** Bind the settings so that evaluated code can modiy them */
  def bindSettings() {
    interpreter.beQuietDuring {
      interpreter.compileString(InterpreterSettings.sourceCodeForClass)
    
      interpreter.bind(
        "settings",
        "scala.tools.nsc.InterpreterSettings", 
        isettings)
    }
  }

  /** interpret all lines from a specified file */
  def interpretAllFrom(filename: String) {
    val fileIn = try {
      new FileReader(filename)
    } catch {
      case e: IOException =>
        out.println("@|bold,red ERROR| " + e.getMessage)
        return
    }
    val oldShell = shell
    try {
      val inFile = new java.io.BufferedReader(fileIn)
      shell = new SimpleInteractiveShell(inFile)
      out.println("@|bold Loading " + filename + "...|")
      out.flush
      repl
    } finally {
      shell = oldShell
      fileIn.close
    }
  }

  /** The main read-eval-print loop for the interpreter.  It calls
   *  <code>command()</code> for each line of input, and stops when
   *  <code>command()</code> returns <code>false</code>.
   */
  def repl() {
    var first = true
    while (true) {
      out.flush()

      val line =
        if (first) {
          val futLine = scala.concurrent.ops.future(shell.prompt())

          bindSettings()
          first = false

          futLine()
      } else {
        shell.prompt()
      }

      if (line eq null)
        return ()  // assumes null means EOF

      val (keepGoing, finalLineMaybe) = command(line)

      if (!keepGoing)
        return

    }
  }

  def command(line: String): (Boolean, Option[String]) = {

    var shouldReplay: Option[String] = None

    try {
      if (ShellSystem.isCommand(line))
        ShellSystem.execute(line)
      else
        shouldReplay = interpretStartingWith(line)

    } catch {
      case e: CommandException => println("@|bold,red ERROR| " + e.getMessage)
      case e => {
       println("@|bold,red ERROR| " + e.getMessage)
       e.printStackTrace
      }
    }
    (true, shouldReplay)
  }
  
  /** Interpret expressions starting with the first line.
    * Read lines until a complete compilation unit is available
    * or until a syntax error has been seen.  If a full unit is
    * read, go ahead and interpret it.  Return the full string
    * to be recorded for replay, if any.
    */
  def interpretStartingWith(code: String): Option[String] = {
    interpreter.interpret(code) match {
      case InterpreterResults.Success => Some(code)
      case InterpreterResults.Error => None
      case InterpreterResults.Incomplete =>
        if (code.endsWith("\n\n")) {
          out.println("You typed two blank lines.  Starting a new command.")
          None
        } else {
          val nextLine = shell.incomplete()
          if (nextLine == null)
            None  // end of file
          else
            interpretStartingWith(code + "\n" + nextLine)
        }
    }
  }

  def run() {
    parentClassLoader0 = classOf[ScalaShell].getClassLoader

    createInterpreter()

    try {
      if (interpreter.reporter.hasErrors) {
        return // it is broken on startup; go ahead and exit
      }

      if (scalarc.exists) {
        interpretAllFrom(scalarc.toString)
      }

      repl()

    } finally {
      closeInterpreter()
    }
  }

}
